/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.room.solver.query.result

import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.beginForEachControlFlow
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XArrayType
import androidx.room.ext.getToArrayFunction
import androidx.room.solver.CodeGenScope

class ArrayQueryResultAdapter(
    private val arrayType: XArrayType,
    private val listResultAdapter: ListQueryResultAdapter
) : QueryResultAdapter(listResultAdapter.rowAdapters) {
    private val componentTypeName: XTypeName = arrayType.componentType.asTypeName()
    private val arrayTypeName = XTypeName.getArrayName(componentTypeName)

    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
        scope.builder.apply {
            val listVarName = scope.getTmpVar("_listResult")
            // Delegate to the ListQueryResultAdapter to convert query result to a List.
            listResultAdapter.convert(listVarName, cursorVarName, scope)

            // Initialize _result to be returned, using the list result we have.
            val tmpArrayResult = scope.getTmpVar("_tmpArrayResult")

            val assignCode = XCodeBlock.of(
                language = language,
                format = "%L",
                listVarName
            ).let {
                when (language) {
                    CodeLanguage.KOTLIN -> {
                        if (componentTypeName.isPrimitive) {
                            // If we have a primitive array like LongArray or ShortArray,
                            // we use conversion functions like toLongArray() or toShortArray().
                            XCodeBlock.of(
                                language = language,
                                format = "%L.%L",
                                it,
                                getToArrayFunction(componentTypeName)
                            )
                        } else {
                            XCodeBlock.of(
                                language = language,
                                format = "%L.%L",
                                it,
                                "toTypedArray()"
                            )
                        }
                    }
                    CodeLanguage.JAVA -> {
                        if (componentTypeName.isPrimitive) {
                            // In Java, initializing an Array using a List is not
                            // straightforward, and requires we create an empty array that will be
                            // initialized using the list contents.
                            addLocalVariable(
                                name = tmpArrayResult,
                                typeName = arrayTypeName,
                                assignExpr = XCodeBlock.of(
                                    language = language,
                                    format = "new %T[%L.size()]",
                                    componentTypeName,
                                    listVarName
                                )
                            )
                            // If the array is primitive, we have to loop over the list to copy
                            // contents, as we cannot use toArray() on primitive array types.
                            val indexVarName = scope.getTmpVar("_index")
                            addLocalVariable(
                                name = indexVarName,
                                typeName = componentTypeName,
                                isMutable = true,
                                assignExpr = XCodeBlock.of(language, "0")
                            )
                            val itrVar = scope.getTmpVar("_listItem")
                            beginForEachControlFlow(
                                iteratorVarName = listVarName,
                                typeName = componentTypeName,
                                itemVarName = itrVar
                            ).apply {
                                addStatement(
                                    "%L[%L] = %L",
                                    tmpArrayResult,
                                    indexVarName,
                                    itrVar
                                )
                                addStatement("%L++", indexVarName)
                            }.endControlFlow()
                            XCodeBlock.of(
                                language = language,
                                format = "%L",
                                tmpArrayResult
                            )
                        } else {
                            // If the array is not primitive, we use the List.toArray() utility.
                            XCodeBlock.of(
                                language = language,
                                format = "%L.toArray(new %T[0])",
                                listVarName,
                                componentTypeName
                            )
                        }
                    }
                }
            }
            addLocalVariable(
                name = outVarName,
                typeName = arrayTypeName,
                assignExpr = assignCode
            )
        }
    }

    override fun isMigratedToDriver(): Boolean = true
}
